/**
 * 
 */
package smallChanges.statements;

import java.util.HashSet;
import java.util.Set;

import net.sourceforge.pmd.ast.ASTBlock;
import net.sourceforge.pmd.ast.ASTBlockStatement;
import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.ast.ASTExplicitConstructorInvocation;
import net.sourceforge.pmd.ast.ASTFormalParameter;
import net.sourceforge.pmd.ast.ASTFormalParameters;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.ast.ASTName;
import net.sourceforge.pmd.ast.ASTResultType;
import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.ast.AccessNode;
import net.sourceforge.pmd.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.ast.Node;
import net.sourceforge.pmd.ast.SimpleNode;
import smallChanges.Tokenization.TokenSet;

public class MethodSignatureVisitor extends JavaParserVisitorAdapter
{
	private TokenSet tokenSet;
	public MethodSignatureVisitor(TokenSet set) {
		this.tokenSet=set;
	}
	
	/* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTConstructorDeclaration, java.lang.Object)
     */
    @Override
    public Object visit(ASTConstructorDeclaration node, Object data)
    {
        MethodSignature methodSignature=new MethodSignature(tokenSet);
        methodSignature.setVisibility(SCVisibility.createSCVisibility(node));
        // Method name is same as the class
        // traverse parent pointers until we get to the class
        // or store the class earlier
        methodSignature.setCoordinatesFromNode(node);
        
        if (methodSignature.getVisibility()==SCVisibility.PUBLIC ||
                methodSignature.getVisibility()==SCVisibility.PRIVATE ||
                methodSignature.getVisibility()==SCVisibility.PROTECTED)
        {
            methodSignature.expandByOnePrecedingToken();
        }
        
        // We need to look up until we find out class name
        // or we hit NULL
        // TODO pass in the classname in BlockStatementVisitor from
        // the top of the stack
        Node parent = node.getNthParent(1);
        
        while( parent != null && !( parent.getClass() == ASTClassOrInterfaceDeclaration.class &&
        		((ASTClassOrInterfaceDeclaration) parent).getImage() != null ) )
        {
        	if( parent instanceof SimpleNode)
        	{
        		parent = ((SimpleNode) parent).getNthParent(1);
        	}
        	else
        	{
        		parent = null;
        	}
        }
        if( parent!= null && parent.getClass() == ASTClassOrInterfaceDeclaration.class &&
        		((ASTClassOrInterfaceDeclaration) parent).getImage() != null )
        {
        	methodSignature.setName(((ASTClassOrInterfaceDeclaration)parent).getImage());

        }
        // continue visiting for parameters and exceptions
        super.visit(node, methodSignature);
        return methodSignature;
    }



    /* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTMethodDeclaration, java.lang.Object)
	 */
	@Override
	public Object visit(ASTMethodDeclaration node, Object data)
	{
		MethodSignature methodSignature=new MethodSignature(tokenSet);
		setVisibilityAndModifiers(node, methodSignature);
		
		super.visit(node, methodSignature);

		return methodSignature;
	}
	
	/**
	 * @param node
	 * @param statement
	 */
	static void setVisibilityAndModifiersNoCoords(
	        AccessNode node,
            IAccessNode statement)
    {
	    statement.setVisibility(SCVisibility.createSCVisibility(node));
        statement.setModifiers(SCModifiers.createSCModifiers(node));
        
        // Now fix the range to grab the IMPLICIT modifier tokens
        // such as public/private, static, final, abstract, synchronized
        // note that we're not necessarily worried about the order
        // we're just making sure we get the correct number of tokens
        // for example, if the method is public, we'll get one preceding
        // token; by induction, so long as we correctly process
        // each keyword, we will get all of the modifier tokens.
        //expandToIncludeImplicitTokens(node, statement);
        expandToIncludeImplicitTokens3(node, statement);
    }
	
    /**
     * Expand the given statement to include the "implicit" tokens
     * specified by node.  Implicit tokens include visibility modifiers
     * and other modifiers such as static, final, abstract, and so on.
     * <p>
     * Essentially, we count the number of modifiers and expand the statement
     * by one token for each modifier that is mentioned.  We don't match up
     * the actual text of the modifier with the token we grab, we just
     * make sure to grab the appropriate number of tokens.
     * <p>
     * One special case is the 'abstract' modifier, which is implied if the
     * method occurs inside an interface, but may also be used anyway.
     * 
     * @param node
     * @param statement
     */
    public static void expandToIncludeImplicitTokens(AccessNode node,
            IAccessNode statement)
    {
        Set<String> set=new HashSet<String>();
        if (node.isStatic()) {
            set.add(statement.expandByOnePrecedingToken());
        }
        if (node.isFinal()) {
            set.add(statement.expandByOnePrecedingToken());
        }
        if (node.isSynchronized()) {
            set.add(statement.expandByOnePrecedingToken());
        }
        if (node.isNative()) {
            set.add(statement.expandByOnePrecedingToken());
        }
        if (node.isVolatile()) {
            set.add(statement.expandByOnePrecedingToken());
        }
        
        boolean foundVisibilityToken=false;
        
        if (node.isAbstract()) {
            int previousBeginLine=statement.getBeginLine();
            int previousBeginColumn=statement.getBeginColumn();
            String token=statement.expandByOnePrecedingToken();
            if (token.equals("public") || token.equals("private") ||
                    token.equals("protected")) 
            {
                // we found the visibility token instead
                foundVisibilityToken=true;
            } else {
                // we found some other token and we need to put it back
                // and we can return
                statement.setBeginLine(previousBeginLine);
                statement.setBeginColumn(previousBeginColumn);
                statement.setTokensFromRange();
                return;
            }
        }
        // at this point, we've handled abstract, and either gotten an abstract token
        // or grabbed the public token instead
        if (statement.getVisibility().requiresToken()) {
            // requires a visibility token
            // we may have already found this
            if (foundVisibilityToken) {
                // OK, we already got the visibility token above
                // try to grab an abstract token
                statement.expandByOnePrecedingTokenConditionally("abstract");
            } else {
                // we haven't already grabbed the visibility token
                // try to grab a visibility token from the next token
                int previousBeginLine=statement.getBeginLine();
                int previousBeginColumn=statement.getBeginColumn();
                String token=statement.expandByOnePrecedingToken();
                if (!token.equals("public") && !token.equals("private") &&
                        !token.equals("protected"))
                {
                    // this token wasn't what we wanted, so put it back and exit
                    statement.setBeginLine(previousBeginLine);
                    statement.setBeginColumn(previousBeginColumn);
                    statement.setTokensFromRange();
                    return;
                }
            }
        }
    }
    private static Set<String> expectedTokens;
    public static void expandToIncludeImplicitTokens3(AccessNode node,
            IAccessNode statement)
    {
        if (expectedTokens==null) {
            expectedTokens=new HashSet<String>();
            expectedTokens.add("abstract");
            expectedTokens.add("final");
            expectedTokens.add("native");
            expectedTokens.add("public");
            expectedTokens.add("private");
            expectedTokens.add("protected");
            expectedTokens.add("static");
            expectedTokens.add("synchronized");
            expectedTokens.add("transient");
            expectedTokens.add("volatile");
        }
        boolean foundExpectedToken=false;
        if (statement.getVisibility().requiresToken()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
        if (foundExpectedToken && node.isAbstract()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
        if (foundExpectedToken && node.isFinal()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
        if (foundExpectedToken && node.isNative()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
        if (foundExpectedToken && node.isStatic()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
        if (foundExpectedToken && node.isSynchronized()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
        if (foundExpectedToken && node.isTransient()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
        if (foundExpectedToken && node.isVolatile()) {
            foundExpectedToken=
                statement.expandByOnePrecedingTokenConditionally(expectedTokens);
        }
    }
    
    public static void expandToIncludeImplicitTokens2(AccessNode node,
            IAccessNode statement)
    {
        int originalBeginLine=statement.getBeginLine();
        int originalBeginColumn=statement.getBeginColumn();
        // Now grab a token for each token we might need to grab
        // and put them into a set
        
        Set<String> set=new HashSet<String>();
        // abstract
        set.add(statement.expandByOnePrecedingToken());
        // static
        set.add(statement.expandByOnePrecedingToken());
        // final
        set.add(statement.expandByOnePrecedingToken());
        // visibility
        set.add(statement.expandByOnePrecedingToken());
        // synchronized
        set.add(statement.expandByOnePrecedingToken());
        // native
        set.add(statement.expandByOnePrecedingToken());
        // volatile
        set.add(statement.expandByOnePrecedingToken());
        
        // now reset the statement to its previous position
        statement.setBeginLine(originalBeginLine);
        statement.setBeginColumn(originalBeginColumn);
        statement.setTokensFromRange();
        // now for each of the expected tokens we've actually seen, 
        // go ahead and expand the official range by one token
        // This is necessary because there are "implicit" modifiers
        // such as 'public abstract' for interface methods
        // and 'public static final' for interface fields
        // each of which may or may not be present, in any order
        if (set.contains("abstract")) {
            statement.expandByOnePrecedingToken();
        }
        if (set.contains("synchronized")) {
            statement.expandByOnePrecedingToken();
        }
        if (set.contains("volatile")) {
            statement.expandByOnePrecedingToken();
        }
        if (set.contains("static")) {
            statement.expandByOnePrecedingToken();
        }
        if (set.contains("native")) {
            statement.expandByOnePrecedingToken();
        }
        if (set.contains("final")) {
            statement.expandByOnePrecedingToken();
        }
        if (set.contains("public") || set.contains("private") || 
                set.contains("protected"))
        {
            statement.expandByOnePrecedingToken();
        }
        
    }
	
	static void setVisibilityAndModifiers(
	        AccessNode node,
	        IAccessNode statement)
	{
		// Set the beginning coordinates for the method declaration
		// NOTE:  This also sets the ending coords, but those will be over-ridden
		// later by other visitor methods in this class
		statement.setCoordinatesFromNode(node);
		
		setVisibilityAndModifiersNoCoords(node, statement);
	}

	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTMethodDeclarator, java.lang.Object)
	 */
	@Override
	public Object visit(ASTMethodDeclarator node, Object data)
	{
		MethodSignature methodSignature=(MethodSignature)data;
		methodSignature.setName(node.getImage());
		
		// use the end of this node to set the final token of the method header
		// this may be updated by the thrown exceptions
		methodSignature.setEndCoordsFromNode(node);
		
		return super.visit(node, data);
	}
	
	/* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTFormalParameters, java.lang.Object)
     */
    @Override
    public Object visit(ASTFormalParameters node, Object data)
    {
        MultiTokenCodeRange2D methodSignature=(MultiTokenCodeRange2D)data;
        methodSignature.setEndCoordsFromNode(node);
        return super.visit(node, data);
    }

    /* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTFormalParameter, java.lang.Object)
	 */
	@Override
	public Object visit(ASTFormalParameter node, Object data)
	{
		MethodSignature methodSignature=(MethodSignature)data;
		
		SCFormalParameter formalParameter=new SCFormalParameter(tokenSet);
		formalParameter.setCoordinatesFromNode(node);
		
		SCType parameterType=(SCType)node.childrenAccept(new TypeVisitor(tokenSet), new SCType(tokenSet));
		
		formalParameter.setType(parameterType);
		formalParameter.setKind(parameterType.getKind());
		formalParameter.setArrayType(parameterType.isArrayType());
		
		Object result=super.visit(node, formalParameter);
		methodSignature.addFormalParameter(formalParameter);
		return result;
	}

	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTVariableDeclaratorId, java.lang.Object)
	 */
	@Override
	public Object visit(ASTVariableDeclaratorId node, Object data)
	{
		if (data instanceof SCFormalParameter) {
			SCFormalParameter formalParameter=(SCFormalParameter)data;
			formalParameter.setName(node.getImage());
		}
		return super.visit(node, data);
	}

	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTName, java.lang.Object)
	 */
	@Override
	public Object visit(ASTName node, Object data)
	{
		// thrown exceptions
		if (data instanceof MethodSignature) {
			MethodSignature methodSignature=(MethodSignature)data;
			methodSignature.addExceptionThrown(node.getImage());
			// update the last token using the begin/end line/col ranges of this node
			methodSignature.setEndCoordsFromNode(node);
		}

		return super.visit(node, data);
	}
	
	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTResultType, java.lang.Object)
	 */
	@Override
	public Object visit(ASTResultType node, Object data)
	{
		if (data instanceof MethodSignature) {
			MethodSignature methodSignature=(MethodSignature)data;
			if (node.isVoid()) {
				methodSignature.setVoid(true);
			} else {
				methodSignature.setVoid(false);
				SCType returnType=(SCType)node.childrenAccept(new TypeVisitor(tokenSet), new SCType(tokenSet));
				methodSignature.setReturnType(returnType);
			}
		}
		return super.visit(node, data);
	}

	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTBlock, java.lang.Object)
	 */
	@Override
	public Object visit(ASTBlock node, Object data)
	{
		return data;
	}

	/* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTBlockStatement, java.lang.Object)
     */
    @Override
    public Object visit(ASTBlockStatement node, Object data)
    {
        return data;
    }

    @Override
    public Object visit(ASTExplicitConstructorInvocation node, Object data)
    {
        return data;
    }
}